package shining365.programmableweb.data.app;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.bind.JAXBException;

import org.apache.commons.configuration.Configuration;

import shining365.programmableweb.data.common.Utils;
import shining365.programmableweb.data.model.Api;
import shining365.programmableweb.data.model.Mashup;
import shining365.programmableweb.data.model.Mashups;

public abstract class AppBasic {
	
	protected static final Configuration config = Utils.getConfiguration();
	
	protected final List<Api> apis;
	protected final double graph[][];
	protected final int nNodes;
	protected final double[] strengths;
	protected final int[] degrees;
	protected final int[] useCounts;
	
	private final Map<Api, Integer> api2index = new HashMap<Api, Integer>();
	
	public enum OrderBy {
		STRENGTH, DEGREE, USE_COUNT
	}
	
	protected AppBasic(boolean countSelfLoop, OrderBy orderBy)
			throws IOException, JAXBException {
		this(new Date(), countSelfLoop, orderBy);
	}
	
	protected AppBasic(Date endDate, boolean countSelfLoop, OrderBy orderBy)
			throws IOException, JAXBException {
		List<Mashup> mashupList = getMashupList(endDate, countSelfLoop);
		
		Set<Api> apiSet = new HashSet<Api>();
		for (Mashup mashup : mashupList) {
			apiSet.addAll(mashup.getApi());
		}
		apis = new ArrayList<Api>(apiSet);
		nNodes = apis.size();
		
		final Map<Api, Integer> api2id = new HashMap<Api, Integer>();
		for (int id = 0; id < nNodes; id++) {
			api2id.put(apis.get(id), id);
		}
		
		//构造没有排序的图
		double[][] tempGraph = new double[nNodes][nNodes];
		Integer[] tempUseCounts = new Integer[nNodes];
		Arrays.fill(tempUseCounts, 0);
		for (Mashup mashup : mashupList) {
			List<Api> usedApis = mashup.getApi();
			
			for (Api a : usedApis) {
				tempUseCounts[api2id.get(a)]++;
			}
			
			if (usedApis.size() == 1) {
				int id = api2id.get(usedApis.get(0));
				//在对角线上，是自环
				tempGraph[id][id] += 0.5;
				continue;
			}
			
			double increment = 1.0 / (usedApis.size() - 1);
			//任意两个用到的api之间加权
			for (int i = 0; i < usedApis.size() - 1; i++) {
				int srcId = api2id.get(usedApis.get(i));
				for (int j = i + 1; j < usedApis.size(); j++) {
					int destId = api2id.get(usedApis.get(j));
					//不在对角线上，有两个对称的位置
					tempGraph[srcId][destId] += increment;
					tempGraph[destId][srcId] += increment;
				}
			}
		}
		
		Double[] tempStrengths = new Double[nNodes];
		Arrays.fill(tempStrengths, 0.0);
		Integer[] tempDegrees = new Integer[nNodes];
		Arrays.fill(tempDegrees, 0);
		for (int i = 0; i < nNodes; i++) {
			int id = api2id.get(apis.get(i));
			tempStrengths[i] = tempGraph[id][id];
			for (double weight : tempGraph[id]) {
				if (weight > 0) {
					tempStrengths[i] += weight;
					tempDegrees[i]++;
				}
			}
		}
		
		//按strength或degree评分
		final List<Number> scores = new ArrayList<Number>();
		switch (orderBy) {
		case STRENGTH:
			scores.addAll(Arrays.asList(tempStrengths));
			break;
		case DEGREE:
			scores.addAll(Arrays.asList(tempDegrees));
			break;
		case USE_COUNT:
			scores.addAll(Arrays.asList(tempUseCounts));
			break;
		default:
			throw new IllegalArgumentException("Illegal orderBy argument");
		}
		//对api按评分降序排序
		Collections.sort(apis, new Comparator<Api>(){
			@Override
			public int compare(Api api1, Api api2) {
				int id1 = api2id.get(api1), id2 = api2id.get(api2);
				return (int)Math.signum(scores.get(id2).doubleValue() - 
						scores.get(id1).doubleValue());
			}
		});
		
		//建索引，以便快速取得序号
		for (int i = 0; i < apis.size(); i++) {
			api2index.put(apis.get(i), i);
		}
		
		//按顺序拷贝到graph
		graph = new double[nNodes][nNodes];
		strengths = new double[nNodes];
		degrees = new int[nNodes];
		useCounts = new int[nNodes];
		for (int i = 0; i < nNodes; i++) {
			int id = api2id.get(apis.get(i));
			for (Api a : apis) {
				graph[i][indexOf(a)] = tempGraph[id][api2id.get(a)];
			}
			strengths[i] = tempStrengths[api2id.get(apis.get(i))];
			degrees[i] = tempDegrees[api2id.get(apis.get(i))];
			useCounts[i] = tempUseCounts[api2id.get(apis.get(i))];
		}
	}
	
	protected int indexOf(Api api) {
		return api2index.get(api);
	}
	
	private List<Mashup> getMashupList(Date endDate, 
			boolean reserveSingleApiMashup) throws IOException, JAXBException {
		Mashups mashups;
		mashups = Utils.unmarshallFromFile(Mashups.class,
				"doc/simpleprogrammableweb.mashups.xml");
		
		List<Mashup> mashupList = mashups.getMashup();
		if (reserveSingleApiMashup && 
				mashupList.get(mashupList.size() - 1).getDateItem()
				.compareTo(endDate) < 0) {
			return mashupList;
		}
		
		List<Mashup> ret = new ArrayList<Mashup>();
		for (Mashup mashup : mashupList) {
			if (mashup.getDateItem().compareTo(endDate) > 0) {
				break;
			}
			
			if (reserveSingleApiMashup || mashup.getApi().size() >= 2) {
				ret.add(mashup);
			}
		}
		
		return ret;
	}
	
}
